//==============================================================================
//
//   DBigDouble - the arbitrary length double number class in the ofc-library
//
//               Copyright (C) 2005  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2008-07-19 12:28:59 $ $Revision: 1.4 $
//
//==============================================================================

#include "ofc/DBigDouble.h"

#ifdef HAVE_DBIGDOUBLE
#include <gmp.h>
#endif

#include <ctype.h>


#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DTextable.h"
#include "ofc/DParsable.h"
#include "ofc/DSimpleArithmetic.h"
#include "ofc/DComparable.h"
#include "ofc/DText.h"

#if defined(HAVE_LIBGMP) && defined(HAVE_GMP_H)
#define HAVE_DBIGDOUBLE  1
#endif

#ifdef HAVE_DBIGDOUBLE

//
// The big double class implements a number of methods for working with arbitrary large double 
// numbers. The class uses the excellent gmp library for the actual calculations. Note: setting
// the precision (as is done by move: and point:) will result in a realloc)
// 
// @example
//
// #include <stdio.h>
// #include "ofc/DBigDouble.h"
// #include "ofc/DText.h"
// 
// int main(int argc, char *argv[])
// {
//   DBigDouble *dbl1 = [DBigDouble alloc];
//   DBigDouble *dbl2 = [DBigDouble new  ];
//   DBigDouble *dbl3 = [DBigDouble new  ];
//   DText      *str;
// 
//   [dbl1 init :3.14143123423234322343223432 :256]; // Init with pi, with 256 precision bits 
// 
//   [dbl2 move :dbl1];                     // Move dbl1 in dbl2
// 
//   [dbl2 mul  :dbl1];                     // Multiply dbl2 with dbl1 -> PI^2
// 
//   [dbl3 sub  :dbl2 :dbl1];               // dbl3 = dbl2 - dbl1 = PI^2 - PI
// 
//   printf("PI^2 - PI=%f\n", [dbl3 get]);  // Get dbl3 as double number
// 
//   str = [dbl3 get :10 :15];              // Convert to string, base 10, 15 digits
// 
//   printf("PI^2 - PI=%s\n", [str cstring]);
// 
//   if ([dbl1 compare :dbl2] == 0)         // Compare numbers
//     printf("Double1 is equal to double2\n");
//   else if ([dbl1 compare :dbl2] < 0)
//     printf("Double1 is smaller than double2\n");
//   else
//     printf("Double1 is greater than double2\n");
// 
//   [str  free];                           // Cleanup
//   [dbl3 free];
//   [dbl2 free];
//   [dbl1 free];
// 
//   return 0;
// }
// 


@interface DBigDouble : Object <DTextable,DParsable,DSimpleArithmetic,DComparable>
{
@private
  void        *_value;      // the big double value
}

#endif
#endif



#ifdef HAVE_DBIGDOUBLE


@implementation DBigDouble

#if _PRIVATE_

extern void _gmpInit(); // defined in DBigInt..

#endif


//// Constructors

//
// Initialise to double zero with default precision 
//
// @return the object
//

- (DBigDouble *) init
{
  [super init];

  _gmpInit();
  
  _value = objc_malloc(sizeof(mpf_t));
  
  mpf_init((mpf_ptr) _value);
  
  return self;
}

//
// Initialise to double zero with a precision
//
// @param point    the number of bits in the precision
//
// @return the object
//

- (DBigDouble *) init :(unsigned long) point
{
  [super init];

  _gmpInit();
  
  _value = objc_malloc(sizeof(mpf_t));
  
  mpf_init2((mpf_ptr) _value, point);

  return self;
}

//
// Initialise to a double with a precision
//
// @param value  the double value
// @param point  the number of bits in the precision
//
// @return the object
//

- (DBigDouble *) init :(double) value :(unsigned long) point
{
  [self init :point];

  [self set :value];

  return self;
}

//
// Initialise with a string
// 
// @param cstring  the cstring with the double
// @param base     the base for the double
// @param point    the number of bits in the precision
// 
// @return the object
// 
- (DBigDouble *) init :(const char *) cstring :(int) base :(unsigned long) point
{
  [self init :point];
  
  [self set :cstring :base];
  
  return self;
}


//// Copy constructor

//
// Do a shallow copy of the object
//
// @return the object
//

- shallowCopy
{
  DBigDouble *copy = [super shallowCopy];

  copy->_value  = objc_malloc(sizeof(mpf_t));
  
  mpf_init2((mpf_ptr) copy->_value, mpf_get_prec(_value));
  
  mpf_set((mpf_ptr) copy->_value, _value);
  
  return copy;
}
  


//// Deconstructor

//
// Free a big double
// 
// @return the object
//
- free
{
  mpf_clear((mpf_ptr) _value );
  
  objc_free(_value); _value = nil;
  
  return [super free];
}


// Member methods

//
// Check if the big double is negative
// 
// @return is it?
// 
- (BOOL) isNegative
{
  return (mpf_sgn((mpf_ptr) _value) < 0);
}

//
// Check if the big double is zero
// 
// @return is it ?
// 
- (BOOL) isZero
{
  return (mpf_sgn((mpf_ptr) _value) == 0);
}

//
// Get the current precision of the big double
// 
// @return the number of bits for the precision
// 
- (unsigned long) point
{
  return mpf_get_prec((mpf_ptr) _value);
}

//
// Set the precision for the big double
// 
// @param point    the new value for the precision (in bits)
// 
// @return the objectc
// 
- (DBigDouble *) point :(unsigned long) point
{
  mpf_set_prec((mpf_ptr) _value, point);
  
  return self;
}


//// Set methods

//
// Set the big double to zero
// 
// @return the object
// 
- (DBigDouble *) clear
{
  mpf_set_d((mpf_ptr) _value, 0.0);

  return self;
}

//
// Set the big double with a (small) double
//
// @param value   the (small) double
//
// @return the object
//
- (DBigDouble *) set :(double) value
{
  mpf_set_d((mpf_ptr) _value, value);
  
  return self;
}

//
// Set the big double with a text number (format: MeN)
//
// @param cstring   the cstring with the double
// @param base      the base of the double (2..36)
//
// @return success
//
- (BOOL) set :(const char *) cstring :(int) base
{
  BOOL ok = NO;
  
  if ((base < 2) || (base > 36))
  {
    WARNING(DW_INVALID_ARG, "base");
  }
  else
  {
    ok = (mpf_set_str((mpf_ptr) _value, cstring, base) != -1);
  }

  return ok;
}

//
// Move an other object in this object (including the precision)
//
// @param other    the other object
//
// @return this object
//
- (DBigDouble *) move :(DBigDouble *) other
{
  if (other == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "other");
  }
  else
  {
    unsigned long precision = mpf_get_prec((mpf_ptr) other->_value);
    
    if (mpf_get_prec((mpf_ptr) _value) != precision)
    {
      mpf_set_prec((mpf_ptr) _value, precision);
    }
  
    mpf_set((mpf_ptr) _value, (mpf_ptr) other->_value);
  }
  
  return self;
}


//// Get methods

//
// Get the big double as (small) double
// 
// @return the (small) double
// 
- (double) get
{
  return mpf_get_d((mpf_ptr) _value);
}

//
// Get the big double as a text string
// 
// @param base     the base for the text string (2..36)
// @param digits   the number of digits in the string (0 = all)
// 
// @return a (new) text string (or nil)
// 
- (DText *) get :(int) base :(unsigned) digits
{
  DText *str = nil;
  
  if ((base < 2) || (base > 36))
  {
    WARNING(DW_INVALID_ARG, "base");
  }
  else
  {
    mp_exp_t exponent;
    
    char *out = mpf_get_str(NULL, &exponent, base, digits, (mpf_ptr) _value);
    
    if (out != NULL)
    {
      str = [DText new];
     
      if (*out == EOS)
      {
        [str set :"0"];
      }
      else
      {
        if (exponent != 0)
        {
          char eChar = (base > 10) ? '@' : 'e';
          
          if (*out == '-')
          {
            [str format :"-0.%s%c%ld", out+1, eChar, exponent];
          }
          else
          {
            [str format :"0.%s%c%ld", out, eChar, exponent];
          }
        }
        else
        {
          if (*out == '-')
          {
            [str format :"-0.%s", out+1];
          }
          else
          {
            [str format :"0.%s", out];
          }
        }
      }

      objc_free(out);
    }
  }
  
  return str;
}


//// Methods on big double numbers

//
// Add with a big double 
//
// @param other    the big double
//
// @return the object
//
- (DBigDouble *) add :(DBigDouble *) other
{
  if (other == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "other");
  }
  else 
  {
    mpf_add((mpf_ptr) _value, (mpf_ptr) _value, (mpf_ptr) other->_value);
  }
  
  return self;
}

//
// Add two big doubles and store the result in the object
//
// @param src1     the first big double
// @param src2     the second big double
//
// @return the object
//

- (DBigDouble *) add :(DBigDouble *) src1 :(DBigDouble *) src2
{
  if ((src1 == nil) || (src2 == nil))
  {
    WARNING(DW_NIL_NOT_ALLOWED, "src?");
  }
  else 
  {
    mpf_add((mpf_ptr) _value, (mpf_ptr) src1->_value, (mpf_ptr) src2->_value);
  }
  
  return self;
}

//
// Subract with a big double
//
// @param other     the big double
//
// @return the object
//

- (DBigDouble *) sub :(DBigDouble *) other
{
  if (other == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "other");
  }
  else
  {
    mpf_sub((mpf_ptr) _value, (mpf_ptr) _value, (mpf_ptr) other->_value);
  }

  return self;
}

//
// Subract two big doubles and store the result in the object
//
// @param src1      the first big double
// @param src2      the second big double
//
// @return the object
//

- (DBigDouble *) sub :(DBigDouble *) src1 :(DBigDouble *) src2
{
  if ((src1 == nil) || (src2 == nil))
  {
    WARNING(DW_NIL_NOT_ALLOWED, "src?");
  }
  else
  {
    mpf_sub((mpf_ptr) _value, (mpf_ptr) src1->_value, (mpf_ptr) src2->_value);
  }

  return self;
}

//
// Multiply with a big double
//
// @param other    the big double
//
// @return the object
//

- (DBigDouble *) mul :(DBigDouble *) other
{ 
  if (other == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "other");
  }
  else
  {
    mpf_mul((mpf_ptr) _value, (mpf_ptr) _value, (mpf_ptr) other->_value);
  }
  
  return self;
}

//
// Multiply two big doubles and store the result in the object
//
// @param src1    the first big double
// @param src2    the second big double
//
// @return the object
//

- (DBigDouble *) mul :(DBigDouble *) src1 :(DBigDouble *) src2
{ 
  if ((src1 == nil) || (src2 == nil))
  {
    WARNING(DW_NIL_NOT_ALLOWED, "src?");
  }
  else
  {
    mpf_mul((mpf_ptr) _value, (mpf_ptr) src1->_value, (mpf_ptr) src2->_value);
  }
  
  return self;
}

//
// Divide with a big double
//
// @param other    the divider
//
// @return the object
//

- (DBigDouble *) div :(DBigDouble *) other
{
  if (other == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "other");
  }
  else
  {
    mpf_div((mpf_ptr) _value, (mpf_ptr) _value, (mpf_ptr) other->_value);
  }
  
  return self; 
}

//
// Divide two big doubles and store the result in the object
//
// @param src1    the number
// @param src2    the divider
//
// @return the object
//

- (DBigDouble *) div :(DBigDouble *) src1 :(DBigDouble *) src2
{
  if ((src1 == nil) || (src2 == nil))
  {
    WARNING(DW_NIL_NOT_ALLOWED, "src?");
  }
  else
  {
    mpf_div((mpf_ptr) _value, (mpf_ptr) src1->_value, (mpf_ptr) src2->_value);
  }
  
  return self; 
}

//
// Absolute the big double
// 
// @return the object
// 

- (DBigDouble *) abs
{
  mpf_abs((mpf_ptr) _value, (mpf_ptr) _value);
  
  return self;
}

//
// Negate the big double
// 
// @return the object
// 
- (DBigDouble *) negate
{
  mpf_neg((mpf_ptr) _value, (mpf_ptr) _value);
  
  return self;
}

//
// Do a square root on the big double
// 
// @return the object
// 
- (DBigDouble *) sqrt
{
  mpf_sqrt((mpf_ptr) _value, (mpf_ptr) _value);
  
  return self;
}

//
// Raise to the power of the big double
// 
// @param factor   the factor 
// 
// @return the object
// 
- (DBigDouble *) power :(unsigned long) factor
{
  mpf_pow_ui((mpf_ptr) _value, (mpf_ptr) _value, factor);
  
  return self;
}

//
// Ceil the big double
// 
// @return the object
// 
- (DBigDouble *) ceil
{
  mpf_ceil((mpf_ptr) _value, (mpf_ptr) _value);
  
  return self;
}

//
// Floor the big double
// 
// @return the object
// 
- (DBigDouble *) floor 
{
  mpf_floor((mpf_ptr) _value, (mpf_ptr) _value);
  
  return self;
}

//
// Truncate the big double
// 
// @return the object
// 
- (DBigDouble *) trunc
{
  mpf_trunc((mpf_ptr) _value, (mpf_ptr) _value);
  
  return self;
}


//// DComparable protocol implementation

//
// Compare with another big double
//
// @param other    the other big double
// 
// @return the compare result (-1,0,1)
//

- (int) compare :(DBigDouble *) other
{
  int result = 0;
  
  if (other == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "other");
  }
  else
  {
    result = mpf_cmp((mpf_ptr) _value, (mpf_ptr) other->_value);
  }
  
  return result;
}


//// Textable protocol implementation

//
// Convert to a decimal text string
//
// @return a (new) text string with the big double (or nil)
//

- (DText *) toText
{
  return [self get :10 :0];
}


//// DParsable protocol implementation

//
// Parse a string for a big double. Formats: 
// [space]*[+-][0..9]*{.[0..9]*}{[eE][+-][0..9]*}
// 
// @param cstr     the string to be parsed (moved to the first non-parsable char)
// 
// @return the result (0,ERANGE,ENODATA)
// 
- (int) fromString :(char **) cstr
{
  DText *str    = [DText new];
  char  *pntr   = *cstr;
  char  *start  = pntr;
  int    result = ENODATA;
  
  while (isspace(*pntr)) // skip spaces
  {
    pntr++;
  }

  start = pntr;

  if ((*pntr == '+') || (*pntr == '-')) // process sign
  {
    [str push :*pntr++];
  }
  
  while (isdigit(*pntr))
  {
    [str push :*pntr++];
  }
  
  if (*pntr == '.')
  {
    [str push :*pntr++];
    
    while (isdigit(*pntr))
    {
      [str push :*pntr++];
    }
  }
  
  if ((*pntr == 'e') || (*pntr == 'E'))
  {
    [str push :*pntr++];
    
    if ((*pntr == '+') || (*pntr == '-')) // process sign
    {
      [str push :*pntr++];
    }
    
    while (isdigit(*pntr))
    {
      [str push :*pntr++];
    }
  }
  
  
  if (pntr != start)
  {
    if (mpf_set_str((mpf_ptr) _value, [str cstring], 10) != -1)
    {
      result = 0;
      
      *cstr = pntr;
    }
    else
    {
      result = ERANGE;
    }
  }
  
  [str free];
  
  return result;
}

@end

#endif
  
/*===========================================================================*/

